﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Transactions;

namespace IRM.Messaging
{
    public class MessageQueue : IDisposable
    {
        private readonly TimeSpan rescheduleInterval = Properties.Settings.Default.RescheduleInterval;

        private Repository.IMessagesDataContext context;

        public MessageQueue(IDbConnection connection) : this(new Repository.SqlMessagesDataContext(connection))
        {
        }

        public MessageQueue(Repository.IMessagesDataContext context) 
            : this()
        {
            if (context == null)
                throw new ArgumentNullException("context");
            this.context = context;
        }

        private MessageQueue()
        {
            PolicyName = Properties.Settings.Default.DefaultExceptionPolicyName;
        }

        public string PolicyName { get; set; }

        public int NoOfNotProcessedMessages
        {
            get
            {
                return context.Messages.Count((Message m) => m.Status != QueueStatus.Processed && m.Status != QueueStatus.Error);

            }
        }

        public int NoOfInvalidMessages
        {
            get
            {
                return context.Messages.Count((Message m) => m.Status == QueueStatus.Error);
            }
        }

        public void Add(Message message)
        {
            if (message == null)
                throw new ArgumentNullException("message");
            AddRange(message);
        }

        public void AddRange(params Message[] messages)
        {
            if (messages == null)
                throw new ArgumentNullException("messages");
            if (messages.Length == 0)
                throw new ArgumentNullException("messages");

            foreach (var message in messages)
            {
                message.Status = QueueStatus.Added;
                context.Insert(message);
            }

            context.SubmitChanges();
        }

        public void Fail(Message message, Exception excpetion)
        {
            if (message == null)
                throw new ArgumentNullException("message");
            if (excpetion == null)
                throw new ArgumentNullException("exception");

            Exception exceptionToThrow;

            Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ExceptionPolicy.HandleException(new FailedMessageException(message.Id, excpetion), PolicyName, out exceptionToThrow);

            message.Status = QueueStatus.Error;
            message.Error = excpetion.ToString();
            context.SaveStatus(message);

            if (exceptionToThrow != null)
                throw exceptionToThrow;
        }

        public Message Peek()
        {
            Message message = (from m in context.Messages
                              where m.Status == QueueStatus.Added || m.Status == QueueStatus.Rescheduled
                              orderby m.Id
                              select m).FirstOrDefault();

            if (message != null && (message.Status == QueueStatus.Added || DateTime.Now.Subtract(message.Changed) > rescheduleInterval))
            {
                message.Status = QueueStatus.Processesing;
                context.SaveStatus(message);
                return message;
            }

            return null;
        }

        public void Remove(Message message)
        {
            if (message == null)
                throw new ArgumentNullException("message");
            if (message.Status != QueueStatus.Processesing)
                throw new InvalidOperationException();

            message.Status = QueueStatus.Processed;
            context.SubmitChanges();
        }

        public void Reschedule(Message message, Exception exception)
        {
            if (message == null)
                throw new ArgumentNullException("message");
            if (exception == null)
                throw new ArgumentNullException("exception");
            if (message.Status != QueueStatus.Processesing)
                throw new InvalidOperationException();

            message.Status = QueueStatus.Rescheduled;

            if (!message.RescheduleUntil.HasValue)
                message.RescheduleUntil = DateTime.Now.Add(Properties.Settings.Default.RescheduleUntil);

            if (message.RescheduleUntil > DateTime.Now.Add(rescheduleInterval))
                context.SubmitChanges();
            else
                Fail(message, exception);
        }

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool isDisposing)
        {
            this.context.Dispose();
        }

        ~MessageQueue()
        {
            Dispose(false);
        }

        #endregion        
        
    }
}
